description = [[
VMware vCenter Server CVE-2021-21985 Remote Code Execution Vulnerability

This script looks the existence of CVE-2021-21985 based on CLASS/METHOD(s) 
available by default on vCenter e.g. "/ui/h5-vsan/rest/*" sending a POST 
request and looking in response body (200) JSON data.

]]

---
-- @usage
-- nmap -p443 --script CVE-2021-21985.nse <target>
-- @output
-- PORT    STATE SERVICE
-- 443/tcp open  https
-- | CVE-2021-21985: 
-- |   VULNERABLE:
-- |   vCenter 6.5-7.0 RCE
-- |     State: VULNERABLE (Exploitable)
-- |     IDs:  CVE:CVE-2021-21985
-- |       The vSphere Client (HTML5) contains a remote code execution vulnerability due to lack of input 
-- |       validation in the Virtual SAN Health Check plug-in which is enabled by default in vCenter Server.
-- |     Disclosure date: 2021-05-28
-- |     References:
-- |_      https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-21985

author = "Alex Hernandez aka alt3kx <alt3kx@protonmail.com>"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"vuln", "exploit"}

local shortport = require "shortport"
local http = require "http"
local stdnse = require "stdnse"
local string = require "string"
local vulns = require "vulns"

portrule = shortport.http

local PATCHED = "Unauthorized"
local VULNERABLE1 = "result"
local VULNERABLE2 = "error"
local UNAVAILABLE1 = "503 Service Unavailable"
local UNAVAILABLE2 = "The requested resource "

action = function(host, port)

    local vuln = {
        title = "vCenter 6.5-7.0 RCE",
        state = vulns.STATE.NOT_VULN,
        IDS = { CVE = 'CVE-2021-21985' },
		description = [[
The vSphere Client (HTML5) contains a remote code execution vulnerability due to lack of input 
validation in the Virtual SAN Health Check plug-in which is enabled by default in vCenter Server.]], 
		
		references = {
           'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-21985'
       },
       dates = {
           disclosure = {year = '2021', month = '05', day = '28'},
       },

    }   
    
    local report = vulns.Report:new(SCRIPT_NAME, host, port)

    -- CLASS/METHOD(s) available for PoC purposes
    -- local uri = "/ui/h5-vsan/rest/*"  

    local uri = "/ui/h5-vsan/rest/proxy/service/com.vmware.vsan.client.services.capability.VsanCapabilityProvider/getClusterCapabilityData"
    
    local options = {header={}}
    options['header']['User-Agent'] = "Mozilla/5.0 (compatible; vCenter)"

    local postdata = '{"methodInput":[{"type":"ClusterComputeResource","value":null,"serverGuid":null}]}' 

    local response = http.post(host, port, uri, { header = { ["Content-Type"] = "application/json" }}, nil, postdata)

    if response.status and response.body then 

      if response.status == 200 and string.find(response.body, VULNERABLE1) ~= nil then  

          stdnse.debug2("vCenter returned 200, with json data ")
          vuln.state = vulns.STATE.EXPLOIT

        end 

        if response.body and string.find(response.body, VULNERABLE2) ~=nil then 
          stdnse.debug2("vCenter returned 200, with json data errors")
          vuln.state = vulns.STATE.LIKELY_VULN
        end 

        -- Patch add authentication to the Virtual SAN Health Check plugin’s /rest/* endpoints
        -- Source: https://attackerkb.com/topics/X85GKjaVER/cve-2021-21985?referrer=home#rapid7-analysis

        if response.body and string.find(response.body, PATCHED) ~=nil then 
          stdnse.debug2("vCenter returned 401, Unauthorized")
          vuln.state = vulns.STATE.NOT_VULN
        end 

        if response.body and string.find(response.body, UNAVAILABLE1) ~=nil then 
          stdnse.debug2("vCenter returned 503, Service Unavailable")
          vuln.state = vulns.STATE.NOT_VULN
        end 

        if response.body and string.find(response.body, UNAVAILABLE2) ~=nil then 
          stdnse.debug2("vCenter returned 404, The request resource is not available")
          vuln.state = vulns.STATE.NOT_VULN
        end 
        
        else 
          stdnse.debug2("vCenter returned unknow response.")
          vuln.state = vulns.STATE.UNKNOWN
    end
    return report:make_output (vuln)
end

